#ifndef __VANILA_VALUE_HH__
#define __VANILA_VALUE_HH__

#include <iostream>
#include <cstdint>

namespace vanila
{
class Object;
class ObjectClass;
class ObjectDictionary;
class ObjectString;
class ObjectFunction;
class ObjectClosure;
class ObjectUpvalue;
class ObjectNative;
class ObjectInstance;
class ObjectBoundMethod;
class ObjectList;
class ObjectSet;
enum class ObjectType;

enum ValueType
{
    EMPTY,
    NIL,
    BOOL,
    INTEGER,
    DECIMAL,
    OBJECT,
};

class Value
{
public:
    Value(ValueType type = ValueType::EMPTY) noexcept:
        _type{type}, _value{0UL} {}

    Value(bool boolean) noexcept: 
        _type{ValueType::BOOL}, _boolean{boolean} {}

    Value(int64_t integer) noexcept:
        _type{ValueType::INTEGER}, _integer{integer} {}
    
    Value(double decimal) noexcept:
        _type{ValueType::DECIMAL}, _decimal{decimal} {}

    Value(Object* object) noexcept:
        _type{ValueType::OBJECT}, _object{object} {} 

public:
    ValueType type() const noexcept
    { return this->_type; }

    ObjectType objectType() const noexcept;

public:
    bool isNil() const noexcept
    { return this->_type == ValueType::NIL; }

    bool isBoolean() const noexcept
    { return this->_type == ValueType::BOOL; }

    bool isInteger() const noexcept
    { return this->_type == ValueType::INTEGER; }

    bool isDecimal() const noexcept
    { return this->_type == ValueType::DECIMAL; }

    bool isNumber() const noexcept
    { return this->isInteger() || this->isDecimal(); }

    bool isObject() const noexcept
    { return this->_type == ValueType::OBJECT; }

public:
    bool isBoundMethod() const noexcept;
    ObjectBoundMethod* asBoundMethod() const noexcept;

    bool isClass() const noexcept;
    ObjectClass* asClass() const noexcept;

    bool isDictionary() const noexcept;
    ObjectDictionary* asDictionary() const noexcept;

    bool isString() const noexcept;
    ObjectString* asString() const noexcept;

    bool isFunction() const noexcept;
    ObjectFunction* asFunction() const noexcept;

    bool isInstance() const noexcept;
    ObjectInstance* asInstance() const noexcept;

    bool isClosure() const noexcept;
    ObjectClosure* asClosure() const noexcept;

    bool isUpvalue() const noexcept;
    ObjectUpvalue* asUpvalue() const noexcept;

    bool isNative() const noexcept;
    ObjectNative* asNative() const noexcept;

    bool isList() const noexcept;
    ObjectList* asList() const noexcept;

    bool isSet() const noexcept;
    ObjectSet* asSet() const noexcept;
    
public:
    bool boolean() const noexcept
    { return this->_boolean; }

    int64_t integer() const noexcept
    { return this->_integer; }

    double decimal() const noexcept
    { return this->_decimal; }

    Object* object() const noexcept
    { return this->_object; }

public:
    //! \brief print the value to console
    void print(bool callStr = true) const;

    //! \brief get the value's hash value
    int64_t hash() const;

    //! \brief wheather this == that
    bool equal(const Value& that) const;

    //! \brief wheather this < that
    bool less(const Value& that) const;

    //! \brief wheather this > that
    bool greater(const Value& that) const;

    //! \brief add two value
    Value add(const Value& that) const;

    //! \brief sub two value
    Value sub(const Value& that) const;

    //! \brief mul two value
    Value mul(const Value& that) const;

    //! \brief div two value
    Value div(const Value& that) const;

private:
    ValueType _type;
    union
    {
        uint64_t _value;
        bool _boolean;
        int64_t _integer;
        double _decimal;
        Object* _object;
    };
};

using NativeFunction = Value(*)(uint32_t, Value*);

//! \brief bool operator for Value, for rb-tree
bool operator > (const Value& v1, const Value& v2);
bool operator < (const Value& v1, const Value& v2);

}

#endif